﻿// Admin.NET 项目的版权、商标、专利和其他相关权利均受相应法律法规的保护。使用本项目应遵守相关法律法规和许可证的要求。
//
// 本项目主要遵循 MIT 许可证和 Apache 许可证（版本 2.0）进行分发和使用。许可证位于源代码树根目录中的 LICENSE-MIT 和 LICENSE-APACHE 文件。
//
// 不得利用本项目从事危害国家安全、扰乱社会秩序、侵犯他人合法权益等法律法规禁止的活动！任何基于本项目二次开发而产生的一切法律纠纷和责任，我们不承担任何责任！

using Admin.NET.Core.Service;
using Microsoft.AspNetCore.Http;
using Furion.DatabaseAccessor;
using Furion.FriendlyException;
using Mapster;
using SqlSugar;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using Admin.NET.Application.Entity;
using Microsoft.Extensions.Logging;
namespace Admin.NET.Application;

/// <summary>
/// 计划反馈服务 🧩
/// </summary>
[ApiDescriptionSettings(ApplicationConst.GroupName, Order = 100)]
public partial class PlanListService : IDynamicApiController, ITransient
{
    private readonly SqlSugarRepository<PlanList> _planListRep;
    private readonly ISqlSugarClient _sqlSugarClient;
    private readonly UserManager _userManager;
    private readonly SysOrgService _sysOrgService;
    private readonly SqlSugarRepository<PlanDelayConfig> _planDelayConfigRep;
    private readonly ILogger<PlanListService> _logger;

    public PlanListService(
        SqlSugarRepository<PlanList> planListRep,
        ISqlSugarClient sqlSugarClient,
        UserManager userManager,
        SysOrgService sysOrgService,
        SqlSugarRepository<PlanDelayConfig> planDelayConfigRep,
        ILogger<PlanListService> logger)
    {
        _planListRep = planListRep;
        _sqlSugarClient = sqlSugarClient;
        _userManager = userManager;
        _sysOrgService = sysOrgService;
        _planDelayConfigRep = planDelayConfigRep;
        _logger = logger;
    }

    /// <summary>
    /// 确保船号有H前缀
    /// </summary>
    /// <param name="shipNumber">船号</param>
    /// <returns>带H前缀的船号</returns>
    private static string EnsureShipNumberPrefix(string shipNumber)
    {
        if (string.IsNullOrWhiteSpace(shipNumber)) return shipNumber;
        return shipNumber.StartsWith("H", StringComparison.OrdinalIgnoreCase) 
            ? shipNumber 
            : "H" + shipNumber;
    }

    /// <summary>
    /// 分页查询计划反馈 🔖
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [DisplayName("分页查询计划反馈")]
    [ApiDescriptionSettings(Name = "Page"), HttpPost]
    public async Task<SqlSugarPagedList<PlanListOutput>> Page(PagePlanListInput input)
    {
        input.Keyword = input.Keyword?.Trim();
        var query = _planListRep.AsQueryable()
            .WhereIF(!string.IsNullOrWhiteSpace(input.Keyword), u => u.Ch.Contains(input.Keyword) || u.Fd.Contains(input.Keyword) || u.Team.Contains(input.Keyword) || u.Remark.Contains(input.Keyword) || u.StartDelayReason.Contains(input.Keyword) || u.EndDelayReason.Contains(input.Keyword))
            .WhereIF(!string.IsNullOrWhiteSpace(input.Ch), u => u.Ch.Contains(input.Ch.Trim()))
            .WhereIF(!string.IsNullOrWhiteSpace(input.Fd), u => u.Fd.Contains(input.Fd.Trim()))
            .WhereIF(!string.IsNullOrWhiteSpace(input.Team), u => u.Team.Contains(input.Team.Trim()))
            .WhereIF(!string.IsNullOrWhiteSpace(input.Remark), u => u.Remark.Contains(input.Remark.Trim()))
            .WhereIF(!string.IsNullOrWhiteSpace(input.StartDelayReason), u => u.StartDelayReason.Contains(input.StartDelayReason.Trim()))
            .WhereIF(!string.IsNullOrWhiteSpace(input.EndDelayReason), u => u.EndDelayReason.Contains(input.EndDelayReason.Trim()))
            .WhereIF(input.ClassName != null, u => u.ClassName == input.ClassName)
            .WhereIF(input.WorkAreaId != null, u => u.WorkAreaId == input.WorkAreaId)
            .WhereIF(input.PlanStartRange?.Length == 2, u => u.PlanStart >= input.PlanStartRange[0] && u.PlanStart <= input.PlanStartRange[1])
            .WhereIF(input.PlanEndRange?.Length == 2, u => u.PlanEnd >= input.PlanEndRange[0] && u.PlanEnd <= input.PlanEndRange[1])
            .WhereIF(input.ActualStartRange?.Length == 2, u => u.ActualStart >= input.ActualStartRange[0] && u.ActualStart <= input.ActualStartRange[1])
            .WhereIF(input.ActualEndRange?.Length == 2, u => u.ActualEnd >= input.ActualEndRange[0] && u.ActualEnd <= input.ActualEndRange[1])
            .WhereIF(input.Status.HasValue, u => u.Status == input.Status)
            .LeftJoin<PlanType>((u, className) => u.ClassName == className.Id)
            .LeftJoin<SysOrg>((u, className, workArea) => u.WorkAreaId == workArea.Id)
            .Select((u, className, workArea) => new PlanListOutput
            {
                Id = u.Id,
                Ch = u.Ch,
                Fd = u.Fd,
                ClassName = u.ClassName,
                ClassNameFkDisplayName = $"{className.Name}",
                WorkAreaId = u.WorkAreaId,
                WorkAreaFkDisplayName = $"{workArea.Name}",
                Team = u.Team,
                PlanStart = u.PlanStart,
                PlanEnd = u.PlanEnd,
                ActualStart = u.ActualStart,
                ActualEnd = u.ActualEnd,
                Remark = u.Remark,
                Status = (FinishStatusEnum)u.Status,
                StartDelayReason = u.StartDelayReason,
                EndDelayReason = u.EndDelayReason,
                TenantId = u.TenantId,
                OrgId = u.OrgId,
                IsDelete = u.IsDelete,
                CreateTime = u.CreateTime,
                UpdateTime = u.UpdateTime,
                CreateUserId = u.CreateUserId,
                CreateUserName = u.CreateUserName,
                UpdateUserId = u.UpdateUserId,
                UpdateUserName = u.UpdateUserName,
            });

        var result = await query.ToPagedListAsync(input.Page, input.PageSize);

        // 计算延期天数
        await CalculateDelayDays(result.Items.ToList());
        
        return result;
    }

    /// <summary>
    /// 获取计划反馈详情 ℹ️
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [DisplayName("获取计划反馈详情")]
    [ApiDescriptionSettings(Name = "Detail"), HttpGet]
    public async Task<PlanList> Detail([FromQuery] QueryByIdPlanListInput input)
    {
        return await _planListRep.GetFirstAsync(u => u.Id == input.Id);
    }

    /// <summary>
    /// 增加计划反馈 ➕
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [DisplayName("增加计划反馈")]
    [ApiDescriptionSettings(Name = "Add"), HttpPost]
    public async Task<long> Add(AddPlanListInput input)
    {
        var userOrgId = _userManager.OrgId;
        
        // 确保船号有H前缀
        input.Ch = EnsureShipNumberPrefix(input.Ch);
        
        var entity = input.Adapt<PlanList>();
        if (await _planListRep.IsAnyAsync(u => u.Ch != null && u.Ch == input.Ch && u.Fd != null && u.Fd == input.Fd && u.ClassName != null && u.ClassName == input.ClassName)) throw Oops.Oh("船号,分段,分类唯一已存在");
        
        // 判断是否存在区域数据.如果没有则添加当前登入账号的区域.如果有.则反向
        var resolvedOrgId = entity.WorkAreaId.HasValue && entity.WorkAreaId.Value > 0
            ? entity.WorkAreaId.Value
            : userOrgId;
        entity.WorkAreaId = resolvedOrgId;
        entity.OrgId = resolvedOrgId;
        
        // 判断是否完成
        if (entity.ActualEnd != null) entity.Status = FinishStatusEnum.Finish;
        
        return await _planListRep.InsertAsync(entity) ? entity.Id : 0;
    }

    /// <summary>
    /// 更新计划反馈 ✏️
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [DisplayName("更新计划反馈")]
    [ApiDescriptionSettings(Name = "Update"), HttpPost]
    public async Task Update(UpdatePlanListInput input)
    {
        var userOrgId = _userManager.OrgId;
        
        // 确保船号有H前缀
        input.Ch = EnsureShipNumberPrefix(input.Ch);
        
        if (await _planListRep.IsAnyAsync(u => u.Id != input.Id && u.Ch != null && u.Ch == input.Ch && u.Fd != null && u.Fd == input.Fd && u.ClassName != null && u.ClassName == input.ClassName)) throw Oops.Oh("船号,分段,分类唯一已存在");
        var entity = input.Adapt<PlanList>();
        
        // 判断是否存在区域数据.如果没有则添加当前登入账号的区域.如果有.则反向
        var resolvedOrgId = entity.WorkAreaId.HasValue && entity.WorkAreaId.Value > 0
            ? entity.WorkAreaId.Value
            : userOrgId;
        entity.WorkAreaId = resolvedOrgId;
        entity.OrgId = resolvedOrgId;
        
        // 判断是否完成 - 根据实际完工日期自动设置状态
        if (entity.ActualEnd != null && !string.IsNullOrWhiteSpace(entity.ActualEnd.ToString()))
        {
            entity.Status = FinishStatusEnum.Finish; // 有完工日期则为已完成
        }
        else
        {
            entity.Status = FinishStatusEnum.UnFinish; // 无完工日期则为未完成
        }
        
        await _planListRep.AsUpdateable(entity)
        .IgnoreColumns(u => new {
            u.StartDelayReason,
            u.EndDelayReason,
        })
        .ExecuteCommandAsync();
        
        // 自动更新分段报验逻辑 - 当"大组上下胎"完工时，自动设置对应"分段报验"的开始时间
        await HandleAutoUpdateInspectionPlan(entity);
    }

    /// <summary>
    /// 处理自动更新分段报验逻辑
    /// </summary>
    /// <param name="entity">计划实体</param>
    private async Task HandleAutoUpdateInspectionPlan(PlanList entity)
    {
        try
        {
            // 检查是否为"大组上下胎"且已完工
            if (entity.ActualEnd != null && 
                !string.IsNullOrWhiteSpace(entity.ActualEnd.ToString()) &&
                entity.ClassName != null)
            {
                // 查询分类名称判断是否为"大组上下胎"
                var className = await _planListRep.Context.Queryable<PlanType>()
                    .Where(t => t.Id == entity.ClassName)
                    .Select(t => t.Name)
                    .FirstAsync();

                if (!string.IsNullOrEmpty(className) && className.Contains("大组上下胎"))
                {
                    // 查找对应的"分段报验"计划
                    var inspectionClassId = await _planListRep.Context.Queryable<PlanType>()
                        .Where(t => t.Name.Contains("分段报验"))
                        .Select(t => t.Id)
                        .FirstAsync();

                    if (inspectionClassId > 0)
                    {
                        // 查找同船号同分段的分段报验计划
                        var inspectionPlan = await _planListRep.AsQueryable()
                            .Where(p => p.Ch == entity.Ch && 
                                       p.Fd == entity.Fd && 
                                       p.ClassName == inspectionClassId)
                            .FirstAsync();

                        if (inspectionPlan != null && inspectionPlan.ActualStart == null)
                        {
                            // 设置分段报验的开始时间为大组上下胎的完工时间
                            inspectionPlan.ActualStart = entity.ActualEnd;
                            await _planListRep.AsUpdateable(inspectionPlan)
                                .UpdateColumns(p => p.ActualStart)
                                .ExecuteCommandAsync();
                        }
                    }
                }
            }
        }
        catch (Exception ex)
        {
            // 使用结构化日志记录错误，不影响主流程
            _logger.LogError(ex,
                "自动更新分段报验失败, Ch:{Ch}, Fd:{Fd}, ClassName:{ClassName}, 用户:{UserId}",
                entity.Ch, entity.Fd, entity.ClassName, _userManager.UserId);
        }
    }

    /// <summary>
    /// 删除计划反馈 ❌
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [DisplayName("删除计划反馈")]
    [ApiDescriptionSettings(Name = "Delete"), HttpPost]
    public async Task Delete(DeletePlanListInput input)
    {
        var entity = await _planListRep.GetFirstAsync(u => u.Id == input.Id) ?? throw Oops.Oh(ErrorCodeEnum.D1002);
        await _planListRep.FakeDeleteAsync(entity);   //假删除
        //await _planListRep.DeleteAsync(entity);   //真删除
    }

    /// <summary>
    /// 批量删除计划反馈 ❌
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [DisplayName("批量删除计划反馈")]
    [ApiDescriptionSettings(Name = "BatchDelete"), HttpPost]
    public async Task<int> BatchDelete([Required(ErrorMessage = "主键列表不能为空")]List<DeletePlanListInput> input)
    {
        var exp = Expressionable.Create<PlanList>();
        foreach (var row in input) exp = exp.Or(it => it.Id == row.Id);
        var list = await _planListRep.AsQueryable().Where(exp.ToExpression()).ToListAsync();
   
        return await _planListRep.FakeDeleteAsync(list);   //假删除
        //return await _planListRep.DeleteAsync(list);   //真删除
    }
    
    /// <summary>
    /// 获取下拉列表数据 🔖
    /// </summary>
    /// <returns></returns>
    [DisplayName("获取下拉列表数据")]
    [ApiDescriptionSettings(Name = "DropdownData"), HttpPost]
    public async Task<Dictionary<string, dynamic>> DropdownData(DropdownDataPlanListInput input)
    {
        var classNameData = await _planListRep.Context.Queryable<PlanType>()
            .InnerJoinIF<PlanList>(input.FromPage, (u, r) => u.Id == r.ClassName)
            .Select(u => new {
                Value = u.Id,
                Label = $"{u.Name}"
            }).ToListAsync();
        var workAreaIdData = await _planListRep.Context.Queryable<SysOrg>()
            .InnerJoinIF<PlanList>(input.FromPage, (u, r) => u.Id == r.WorkAreaId)
            .Select(u => new {
                Value = u.Id,
                Label = $"{u.Name}"
            }).ToListAsync();
        return new Dictionary<string, dynamic>
        {
            { "className", classNameData },
            { "workAreaId", workAreaIdData },
        };
    }
    
    /// <summary>
    /// 导出计划反馈记录 🔖
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [DisplayName("导出计划反馈记录")]
    [ApiDescriptionSettings(Name = "Export"), HttpPost, NonUnify]
    public async Task<IActionResult> Export(PagePlanListInput input)
    {
        var list = (await Page(input)).Items?.Adapt<List<ExportPlanListOutput>>() ?? new();
        if (input.SelectKeyList?.Count > 0) list = list.Where(x => input.SelectKeyList.Contains(x.Id)).ToList();
        return ExcelHelper.ExportTemplate(list, "计划反馈导出记录");
    }
    
    /// <summary>
    /// 下载计划反馈数据导入模板 ⬇️
    /// </summary>
    /// <returns></returns>
    [DisplayName("下载计划反馈数据导入模板")]
    [ApiDescriptionSettings(Name = "Import"), HttpGet, NonUnify]
    public IActionResult DownloadTemplate()
    {
        return ExcelHelper.ExportTemplate(new List<ExportPlanListOutput>(), "计划反馈导入模板", (_, info) =>
        {
            if (nameof(ExportPlanListOutput.ClassNameFkDisplayName) == info.Name) return _planListRep.Context.Queryable<PlanType>().Select(u => $"{u.Name}").Distinct().ToList();
            if (nameof(ExportPlanListOutput.WorkAreaFkDisplayName) == info.Name) return _planListRep.Context.Queryable<SysOrg>().Select(u => $"{u.Name}").Distinct().ToList();
            return null;
        });
    }
    
    private static readonly object _planListImportLock = new object();
    /// <summary>
    /// 导入计划反馈记录 💾
    /// </summary>
    /// <returns></returns>
    [DisplayName("导入计划反馈记录")]
    [ApiDescriptionSettings(Name = "Import"), HttpPost, NonUnify, UnitOfWork]
    public IActionResult ImportData([Required] IFormFile file)
    {
        var userOrgId = _userManager.OrgId;
        lock (_planListImportLock)
        {
            var stream = ExcelHelper.ImportData<ImportPlanListInput, PlanList>(file, (list, markerErrorAction) =>
            {
                _sqlSugarClient.Utilities.PageEach(list, 2048, pageItems =>
                {
                    // 链接 分类
                    var classNameLabelList = pageItems.Where(x => x.ClassNameFkDisplayName != null).Select(x => x.ClassNameFkDisplayName).Distinct().ToList();
                    if (classNameLabelList.Any()) {
                        var classNameLinkMap = _planListRep.Context.Queryable<PlanType>().Where(u => classNameLabelList.Contains($"{u.Name}")).ToList()
                            .GroupBy(u => u.Name)
                            .ToDictionary(g => g.Key, g => g.FirstOrDefault()?.Id as long?);
                        pageItems.ForEach(e => {
                            e.ClassName = classNameLinkMap.GetValueOrDefault(e.ClassNameFkDisplayName ?? "");
                            if (e.ClassName == null) e.Error = "分类链接失败";
                        });
                    }
                    // 链接 区域
                    var workAreaIdLabelList = pageItems.Where(x => x.WorkAreaFkDisplayName != null).Select(x => x.WorkAreaFkDisplayName).Distinct().ToList();
                    if (workAreaIdLabelList.Any()) {
                        var workAreaIdLinkMap = _planListRep.Context.Queryable<SysOrg>().Where(u => workAreaIdLabelList.Contains($"{u.Name}")).ToList()
                            .GroupBy(u => u.Name)
                            .ToDictionary(g => g.Key, g => g.FirstOrDefault()?.Id as long?);
                        pageItems.ForEach(e => {
                            e.WorkAreaId = workAreaIdLinkMap.GetValueOrDefault(e.WorkAreaFkDisplayName ?? "");
                            if (e.WorkAreaId == null) e.Error = "区域链接失败";
                        });
                    }
                    
                    // 确保所有船号都有H前缀
                    pageItems.ForEach(e => {
                        e.Ch = EnsureShipNumberPrefix(e.Ch);
                    });
                    
                    // 检查表单内重复数据
                    var duplicateGroups = pageItems.GroupBy(x => new { x.Ch, x.Fd, x.ClassName })
                        .Where(g => g.Count() > 1 && g.Key.Ch != null && g.Key.Fd != null && g.Key.ClassName != null);
                    
                    foreach (var group in duplicateGroups)
                    {
                        var items = group.ToList();
                        for (int i = 1; i < items.Count; i++) // 保留第一个，标记其余为重复
                        {
                            items[i].Error = $"表单内重复数据：船号={group.Key.Ch}, 分段={group.Key.Fd}, 分类={group.Key.ClassName}";
                        }
                    }
                    
                    // 校验并过滤必填基本类型为null的字段
                    var validatedItems = pageItems.Where(x => {
                        if (!string.IsNullOrWhiteSpace(x.Error)) return false;
                        if (x.ClassName == null){
                            x.Error = "分类不能为空";
                            return false;
                        }
                        if (x.WorkAreaId == null){
                            x.Error = "区域不能为空";
                            return false;
                        }
                        return true;
                    }).ToList();
                    
                    // 转换为实体类型
                    var rows = validatedItems.Adapt<List<PlanList>>();
                    
                    // 设置OrgId与WorkAreaId和状态
                    rows.ForEach(entity =>
                    {
                        var resolvedOrgId = entity.WorkAreaId.HasValue && entity.WorkAreaId.Value > 0
                            ? entity.WorkAreaId.Value
                            : userOrgId;
                        entity.WorkAreaId = resolvedOrgId;
                        entity.OrgId = resolvedOrgId;
                        // 判断是否完成 - 根据实际完工日期自动设置状态
                        if (entity.ActualEnd != null && !string.IsNullOrWhiteSpace(entity.ActualEnd.ToString()))
                        {
                            entity.Status = FinishStatusEnum.Finish; // 有完工日期则为已完成
                        }
                        else
                        {
                            entity.Status = FinishStatusEnum.UnFinish; // 无完工日期则为未完成
                        }
                    });
                    
                    var storageable = _planListRep.Context.Storageable(rows)
                        .SplitError(it => string.IsNullOrWhiteSpace(it.Item.Ch), "船号不能为空")
                        .SplitError(it => it.Item.Ch?.Length > 30, "船号长度不能超过30个字符")
                        .SplitError(it => string.IsNullOrWhiteSpace(it.Item.Fd), "分段不能为空")
                        .SplitError(it => it.Item.Fd?.Length > 30, "分段长度不能超过30个字符")
                        .SplitError(it => it.Item.WorkAreaId == null, "区域不能为空")
                        .SplitError(it => it.Item.Team?.Length > 32, "劳务队长度不能超过32个字符")
                        .SplitError(it => it.Item.PlanStart == null, "计划开工不能为空")
                        .SplitError(it => it.Item.PlanEnd == null, "计划完工不能为空")
                        .SplitError(it => it.Item.Remark?.Length > 128, "备注长度不能超过128个字符")
                        .SplitError(it => it.Item.StartDelayReason?.Length > 500, "开工延期原因长度不能超过500个字符")
                        .SplitError(it => it.Item.EndDelayReason?.Length > 500, "完工延期原因长度不能超过500个字符")
                        .WhereColumns(it => new { it.Ch, it.Fd, it.ClassName })
                        .SplitInsert(it=> !it.Any())
                        .SplitUpdate(it=> it.Any())
                        .ToStorage();
                    
                    storageable.AsInsertable.ExecuteCommand();// 不存在插入
                    storageable.AsUpdateable.UpdateColumns(it => new
                    {
                        it.Ch,
                        it.Fd,
                        it.ClassName,
                        it.WorkAreaId,
                        it.OrgId,
                        it.Team,
                        it.PlanStart,
                        it.PlanEnd,
                        it.ActualStart,
                        it.ActualEnd,
                        it.Remark,
                        it.Status,
                        it.StartDelayReason,
                        it.EndDelayReason,
                        it.UpdateUserId,
                        it.UpdateUserName,
                        it.UpdateTime,
                    }).ExecuteCommand();// 存在更新
                    
                    // 标记错误信息
                    markerErrorAction.Invoke(storageable, pageItems, rows);
                });
            });
            
            return stream;
        }
    }

    /// <summary>
    /// 更新延期原因
    /// </summary>
    /// <param name="input">延期原因更新输入参数</param>
    /// <returns></returns>
    [DisplayName("更新延期原因")]
    [ApiDescriptionSettings(Name = "UpdateDelayReason"), HttpPost]
    public async Task UpdateDelayReason(UpdateDelayReasonInput input)
    {
        var entity = await _planListRep.GetFirstAsync(u => u.Id == input.Id);
        if (entity == null)
            throw Oops.Oh("记录不存在");
            
        if (input.Field == "StartDelayReason")
            entity.StartDelayReason = input.Reason;
        else if (input.Field == "EndDelayReason")
            entity.EndDelayReason = input.Reason;
        else
            throw Oops.Oh("字段名无效，只支持 StartDelayReason 或 EndDelayReason");
            
        await _planListRep.AsUpdateable(entity)
            .UpdateColumns(e => new { e.StartDelayReason, e.EndDelayReason })
            .ExecuteCommandAsync();
    }

    /// <summary>
    /// 计算延期天数
    /// </summary>
    /// <param name="planListItems">计划列表</param>
    private async Task CalculateDelayDays(List<PlanListOutput> planListItems)
    {
        if (!planListItems.Any()) return;

        // 获取所有分类的延期配置
        var classNameIds = planListItems.Select(x => x.ClassName).Distinct().ToList();
        var delayConfigs = await _planDelayConfigRep.AsQueryable()
            .Where(x => classNameIds.Contains(x.ClassNameId))
            .ToListAsync();
        var delayConfigDict = delayConfigs.ToDictionary(x => x.ClassNameId, x => x.DelayDays);

        var currentDate = DateTime.Now.Date;

        foreach (var item in planListItems)
        {
            var allowedDelayDays = delayConfigDict.GetValueOrDefault(item.ClassName, 7); // 默认7天

            // 计算开工延期天数：(实际开工或今天) - 计划开工 > 延期配置天数
            if (item.PlanStart.HasValue)
            {
                var planStartDate = item.PlanStart.Value.Date;
                var compareDate = item.ActualStart?.Date ?? currentDate; // 如果没有实际开工，用今天
                var daysDiff = (compareDate - planStartDate).Days;
                if (daysDiff > allowedDelayDays)
                {
                    item.StartDelayDays = daysDiff; // 显示实际延期总天数
                }
            }

            // 计算完工延期天数：(实际完工或今天) - 计划完工 > 延期配置天数
            if (item.PlanEnd.HasValue)
            {
                var planEndDate = item.PlanEnd.Value.Date;
                var compareDate = item.ActualEnd?.Date ?? currentDate; // 如果没有实际完工，用今天
                var daysDiff = (compareDate - planEndDate).Days;
                if (daysDiff > allowedDelayDays)
                {
                    item.EndDelayDays = daysDiff; // 显示实际延期总天数
                }
            }
        }
    }
}
